﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
using System.Reflection;
using System.ComponentModel.DataAnnotations;

namespace Qianke.Core.Common
{
    /// <summary>
    /// 对象扩展方法
    /// </summary>
    public static class ObjectExtend
    {
        #region 模型字段 赋值 获取
        static class ObjectSetAndGet<TModel>
        {
            public static Func<TModel, string, object, bool> FnSetProperty = CreateSetProperty();

            public static Func<TModel, string, object> FnGetProperty = CreateGetProperty();

            private static Func<TModel, string, object, bool> CreateSetProperty()
            {
                var pModel = Expression.Parameter(typeof(TModel), "model");
                var pName = Expression.Parameter(typeof(string), "keyName");
                var pValue = Expression.Parameter(typeof(object), "value");
                var pResult = Expression.Parameter(typeof(bool), "result");

                var lab = Expression.Label(typeof(bool));

                var caseList = new List<SwitchCase>();
                foreach (var p in typeof(TModel).GetProperties().Where(u => u.CanWrite))
                {
                    var left = Expression.Property(pModel, p.Name);
                    var right = Expression.Convert(pValue, p.PropertyType);

                    caseList.Add(
                        Expression.SwitchCase(
                            body: Expression.Block(
                                Expression.Assign(left, right),
                                Expression.Assign(pResult, Expression.Constant(true)),
                                Expression.Return(lab, pResult)
                            ),
                            testValues: Expression.Constant(p.Name, typeof(string))
                        )
                    );
                }
                var expList = new List<Expression>();
                expList.Add(Expression.Switch(pName, caseList.ToArray()));
                expList.Add(Expression.Return(lab, Expression.Constant(false, typeof(bool))));
                expList.Add(Expression.Label(lab, Expression.Constant(false, typeof(bool))));

                var block = Expression.Block(new ParameterExpression[] { pResult }, expList);
                return Expression.Lambda<Func<TModel, string, object, bool>>(block, pModel, pName, pValue).Compile();
            }

            private static Func<TModel, string, object> CreateGetProperty()
            {
                var tType = typeof(TModel);
                var pModel = Expression.Parameter(tType, "model");
                var pName = Expression.Parameter(typeof(string), "keyName");
                var pResult = Expression.Parameter(typeof(object), "result");
                var lab = Expression.Label(typeof(object));

                var caseList = new List<SwitchCase>();
                foreach (var p in tType.GetProperties().Where(u => u.CanRead))
                {
                    var assign = Expression.Assign(pResult, Expression.Convert(Expression.Property(pModel, p.Name), typeof(object)));
                    caseList.Add(
                        Expression.SwitchCase(
                            body: Expression.Block(assign, Expression.Return(lab, pResult)),
                            testValues: Expression.Constant(p.Name, typeof(string))
                        )
                    );
                }
                var expList = new List<Expression>();
                expList.Add(Expression.Assign(pResult, Expression.Constant(null, typeof(object))));
                expList.Add(Expression.Switch(pName, caseList.ToArray()));
                expList.Add(Expression.Return(lab, pResult));
                expList.Add(Expression.Label(lab, Expression.Constant(null, typeof(object))));
                var block = Expression.Block(new ParameterExpression[] { pResult }, expList);
                return Expression.Lambda<Func<TModel, string, object>>(block, pModel, pName).Compile();
            }
        }


        static Dictionary<Type, Func<object, string, object, bool>> FnSetPropertyDict = new Dictionary<Type, Func<object, string, object, bool>>();
        static Dictionary<Type, Func<object, string, object>> FnGetPropertyDict = new Dictionary<Type, Func<object, string, object>>();

        private static Func<object, string, object> CreateGetProperty(Type tType)
        {
            var srcModel = Expression.Parameter(typeof(object), "model");
            var pModel = Expression.Convert(srcModel, tType);

            var pName = Expression.Parameter(typeof(string), "keyName");
            var pResult = Expression.Parameter(typeof(object), "result");
            var lab = Expression.Label(typeof(object));

            var caseList = new List<SwitchCase>();
            foreach (var gp in tType.GetProperties().Where(u => u.CanRead).GroupBy(u => u.Name))
            {
                try
                {
                    var p = gp.ElementAt(0);
                    var assign = Expression.Assign(pResult, Expression.Convert(Expression.Property(pModel, p), typeof(object)));
                    caseList.Add(
                        Expression.SwitchCase(
                            body: Expression.Block(assign, Expression.Return(lab, pResult)),
                            testValues: Expression.Constant(p.Name, typeof(string))
                        )
                    );
                }
                catch (Exception)
                {

                }
            }
            var expList = new List<Expression>();
            expList.Add(Expression.Assign(pResult, Expression.Constant(null, typeof(object))));
            expList.Add(Expression.Switch(pName, caseList.ToArray()));
            expList.Add(Expression.Return(lab, pResult));
            expList.Add(Expression.Label(lab, Expression.Constant(null, typeof(object))));
            var block = Expression.Block(new ParameterExpression[] { pResult }, expList);
            return Expression.Lambda<Func<object, string, object>>(block, srcModel, pName).Compile();
        }

        private static Func<object, string, object, bool> CreateSetProperty(Type tType)
        {
            var srcModel = Expression.Parameter(typeof(object), "model");
            var pModel = Expression.Convert(srcModel, tType);
            var pName = Expression.Parameter(typeof(string), "keyName");
            var pValue = Expression.Parameter(typeof(object), "value");
            var pResult = Expression.Parameter(typeof(bool), "result");

            var lab = Expression.Label(typeof(bool));

            var caseList = new List<SwitchCase>();
            foreach (var gp in tType.GetProperties().Where(u => u.CanWrite).GroupBy(u => u.Name))
            {
                try
                {
                    var p = gp.ElementAt(0);
                    var left = Expression.Property(pModel, p.Name);
                    var right = Expression.Convert(pValue, p.PropertyType);

                    caseList.Add(
                        Expression.SwitchCase(
                            body: Expression.Block(
                                Expression.Assign(left, right),
                                Expression.Assign(pResult, Expression.Constant(true)),
                                Expression.Return(lab, pResult)
                            ),
                            testValues: Expression.Constant(p.Name, typeof(string))
                        )
                    );
                }
                catch (Exception)
                {

                }
            }
            var expList = new List<Expression>();
            expList.Add(Expression.Switch(pName, caseList.ToArray()));
            expList.Add(Expression.Return(lab, Expression.Constant(false, typeof(bool))));
            expList.Add(Expression.Label(lab, Expression.Constant(false, typeof(bool))));

            var block = Expression.Block(new ParameterExpression[] { pResult }, expList);
            return Expression.Lambda<Func<object, string, object, bool>>(block, srcModel, pName, pValue).Compile();
        }


        public static object Get<TModel>(this TModel model, string fieldName)
        {
            var tType = model.GetType();
            Func<object, string, object> fnGet = null;
            lock (FnGetPropertyDict)
            {
                if (FnGetPropertyDict.ContainsKey(tType))
                    fnGet = FnGetPropertyDict[tType];
                else
                {
                    fnGet = CreateGetProperty(tType);
                    FnGetPropertyDict[tType] = fnGet;
                }
            }
            return fnGet(model, fieldName);
        }
        public static bool Set<TModel>(this TModel model, string fieldName, object value)
        {
            var tType = model.GetType();
            Func<object, string, object, bool> fnGet = null;
            lock (FnSetPropertyDict)
            {
                if (FnSetPropertyDict.ContainsKey(tType))
                    fnGet = FnSetPropertyDict[tType];
                else
                {
                    fnGet = CreateSetProperty(tType);
                    FnSetPropertyDict[tType] = fnGet;
                }
            }
            return fnGet(model, fieldName, value);
        }
        #endregion

        #region 模型 整体 复制 拷贝
        static class ObjectAssign<TSrc, TDes>
        {
            public static Action<TSrc, TDes> FnAssign = GetAction();

            public static Action<TSrc, TDes> GetAction()
            {
                var expressList = new List<Expression>();

                var paramSrc = Expression.Parameter(typeof(TSrc), "pSrc");
                var paramDes = Expression.Parameter(typeof(TDes), "pDes");

                var pSrcArray = typeof(TSrc).GetProperties();
                var pDesArray = typeof(TDes).GetProperties();

                foreach (var pSrc in pSrcArray)
                {
                    var pDes = pDesArray.FirstOrDefault(u => u.Name == pSrc.Name && u.PropertyType == pSrc.PropertyType && u.CanWrite);
                    if (pDes == null) continue;

                    var left = Expression.Property(paramDes, pDes);
                    var right = Expression.Property(paramSrc, pSrc);
                    var assign = Expression.Assign(left, Expression.Convert(right, pDes.PropertyType));
                    expressList.Add(assign);
                }

                var block = Expression.Block(expressList);
                var body = Expression.Lambda<Action<TSrc, TDes>>(block, paramSrc, paramDes);
                return body.Compile();
            }

        }

        /// <summary>
        /// 两个不同的模型 对相同字段进行赋值
        /// </summary>
        /// <typeparam name="TSrc"></typeparam>
        /// <typeparam name="TDes"></typeparam>
        /// <param name="des"></param>
        /// <param name="src"></param>
        public static void SetValue<TDes, TSrc>(this TDes des, TSrc src)
        {
            ObjectAssign<TSrc, TDes>.FnAssign(src, des);
        }

        /// <summary>
        /// 拷贝当前模型的值到新模型里
        /// </summary>
        /// <typeparam name="TDes"></typeparam>
        /// <typeparam name="TSrc"></typeparam>
        /// <param name="src"></param>
        /// <returns></returns>
        public static TDes Copy<TSrc, TDes>(this TSrc src)
        {
            var des = Activator.CreateInstance<TDes>();
            des.SetValue(src);
            return des;
        }

        public static TDes Copy<TSrc, TDes>(this TSrc src, out TDes des)
        {
            des = Activator.CreateInstance<TDes>();
            des.SetValue(src);
            return des;
        }

        #endregion

        #region 模型验证

        private static Dictionary<Type, PropertyInfo[]> modelTypeDict = new Dictionary<Type, PropertyInfo[]>();
        private static PropertyInfo[] GetProperties<TModel>(this TModel model)
        {
            var type = model.GetType();
            if (!modelTypeDict.ContainsKey(type))
                modelTypeDict[type] = type.GetProperties();
            return modelTypeDict[type];
        }
        #endregion

    }
}
